/** {{classname}}Data a data transfer object, primarily for simple json serialisation.
  * It has no validation - there may be nulls, values out of range, etc
  */
case class {{classname}}Data(
  {{#vars}}
  {{#description}}
/* {{{description}}} */
  {{/description}}
  {{name}}: {{#isEnum}}{{classname}}.{{datatypeWithEnum}}{{/isEnum}}{{^isEnum}}{{{vendorExtensions.x-datatype-data}}}{{/isEnum}}{{^required}} = {{{vendorExtensions.x-defaultValue-data}}} {{/required}}{{^-last}},{{/-last}}
  {{/vars}}
  {{#isAdditionalPropertiesTrue}}, additionalProperties : ujson.Value =  ujson.Null{{/isAdditionalPropertiesTrue}}

) derives RW {

  def asJsonString: String = asJson.toString()

  def asJson : ujson.Value = {
    val jason = writeJs(this)
    {{#isAdditionalPropertiesTrue}}
    jason.obj.remove("additionalProperties")
    jason.mergeWith(additionalProperties)
    {{/isAdditionalPropertiesTrue}}
    {{^isAdditionalPropertiesTrue}}
    jason
    {{/isAdditionalPropertiesTrue}}
  }

  def validationErrors(path : Seq[Field], failFast : Boolean) : Seq[ValidationError] = {
    val _allValidationErrors = scala.collection.mutable.ListBuffer[ValidationError]()
    {{#vars}}
        // ================== {{name}} validation ==================
        {{#pattern}}
        // validate against pattern '{{{pattern}}}'
        if (_allValidationErrors.isEmpty || !failFast) {
           val regex = """{{{pattern}}}"""
           if {{name}} == null || !regex.r.matches({{name}}) then
              _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' doesn't match pattern $regex")
        }
        {{/pattern}}
        {{#minimum}}
        // validate against {{#exclusiveMinimum}}exclusive {{/exclusiveMinimum}}minimum {{minimum}}
        if (_allValidationErrors.isEmpty || !failFast) {
            if !({{name}} >{{^exclusiveMinimum}}={{/exclusiveMinimum}} {{minimum}}) then
            _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' is not greater than the {{#exclusiveMinimum}}exclusive {{/exclusiveMinimum}}minimum value {{minimum}}")
        }
        {{/minimum}}
        {{#maximum}}
        // validate against {{#exclusiveMaximum}}exclusive {{/exclusiveMaximum}}maximum {{maximum}}
        if (_allValidationErrors.isEmpty || !failFast) {
            if !({{name}} <{{^exclusiveMaximum}}={{/exclusiveMaximum}} {{maximum}}) then
            _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"value '${{name}}' is not greater than the {{#exclusiveMaximum}}exclusive {{/exclusiveMaximum}}maximum value {{maximum}}")
        }
        {{/maximum}}
        {{#minLength}}
        // validate min length {{minLength}}
        if (_allValidationErrors.isEmpty || !failFast)  {
          val len = if {{name}} == null then 0 else {{name}}.length
            if (len < {{minLength}}) {
               _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"length $len is shorter than the min length {{minLength}}")
            }
        }
        {{/minLength}}
        {{#maxLength}}
        // validate max length {{maxLength}}
        if (_allValidationErrors.isEmpty || !failFast)  {
          val len = if {{name}} == null then 0 else {{name}}.length
            if (len < {{maxLength}}) {
               _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"length $len is longer than the max length {{maxLength}}")
            }
        }
        {{/maxLength}}
        {{#isEmail}}
        // validate {{name}} is a valid email address
        if (_allValidationErrors.isEmpty || !failFast) {
            val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"""
            // validate {{name}} is email
            if ({{name}} == null || !emailRegex.r.matches({{name}})) {
              _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"${{name}} is not a valid email address according to the pattern $emailRegex")
            }
        }
        {{/isEmail}}
        {{#required}}{{^isPrimitiveType}}
        if (_allValidationErrors.isEmpty || !failFast) {
            if ({{name}} == null) {
            _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, "{{name}} is a required field and cannot be null")
            }
        }
        {{/isPrimitiveType}}{{/required}}
        {{#uniqueItems}}
          // validate {{name}} has unique items
          if (_allValidationErrors.isEmpty || !failFast) {
              if ({{name}} != null) {
                {{name}}.foldLeft(Set[{{{vendorExtensions.x-containertype-data}}}]()) {
                  case (set, next) if set.contains(next) =>
                    _allValidationErrors += ValidationError(
                      path :+ {{classname}}.Fields.{{name}},
                      s"duplicate value: $next"
                    )
                    set + next
                  case (set, next) => set + next
                }
              }
          }
        {{/uniqueItems}}
        {{#multipleOf}}
        if (_allValidationErrors.isEmpty || !failFast) {
          // validate {{name}} multiple of {{multipleOf}}
          if ({{name}} % {{multipleOf}} != 0) {
            _allValidationErrors += ValidationError(
                  path :+ {{classname}}.Fields.{{name}},
                  s"${{name}} is not a multiple of {{multipleOf}}"
                )
          }
        }
        {{/multipleOf}}
        {{#minItems}}
        // validate min items {{minItems}}
        if (_allValidationErrors.isEmpty || !failFast) {
          val len = if {{name}} == null then 0 else {{name}}.size
            if (len < {{minItems}}) {
               _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"{{name}} has $len, which is less than the min items {{minItems}}")
            }
        }
        {{/minItems}}
        {{#maxItems}}
        // validate min items {{maxItems}}
        if (_allValidationErrors.isEmpty || !failFast) {
          val len = if {{name}} == null then 0 else {{name}}.size
            if (len > {{maxItems}}) {
               _allValidationErrors += ValidationError(path :+ {{classname}}.Fields.{{name}}, s"{{name}} has $len, which is greater than the max items {{maxItems}}")
            }
        }
        {{/maxItems}}
        {{#minProperties}} TODO - minProperties {{/minProperties}}
        {{#maxProperties}} TODO - maxProperties {{/maxProperties}}
        {{#items}}{{#isModel}}
        if (_allValidationErrors.isEmpty || !failFast) {
            if ({{name}} != null) {
                {{name}}.zipWithIndex.foreach {
                    case (value, i) if _allValidationErrors.isEmpty || !failFast =>
                      _allValidationErrors ++= value.validationErrors(
                        path :+ {{classname}}.Fields.{{name}} :+ Field(i.toString),
                        failFast)
                    case (value, i) =>
                }
            }
        }
        {{/isModel}}{{/items}}
        {{#isModel}}
        // validating {{name}}
        if (_allValidationErrors.isEmpty || !failFast) {
            if {{name}} != null then _allValidationErrors ++= {{name}}.validationErrors(path :+ {{classname}}.Fields.{{name}}, failFast)
        }
        {{/isModel}}

    {{/vars}}
    _allValidationErrors.toSeq
  }

  /**
   * @return the validated model within a Try (if successful)
   */
  def validated(failFast : Boolean = false) : scala.util.Try[{{classname}}] = {
    validationErrors(Vector(), failFast) match {
      case Seq() => Success(asModel)
      case first +: theRest => Failure(ValidationErrors(first, theRest))
    }
  }

  /** use 'validated' to check validation */
  def asModel : {{classname}} = {
    {{classname}}(
    {{#vars}}
        {{name}} = {{{vendorExtensions.x-asModel}}}{{^-last}},{{/-last}}
    {{/vars}}
    {{#isAdditionalPropertiesTrue}}, additionalProperties{{/isAdditionalPropertiesTrue}}
    )
  }
}

object {{classname}}Data {

  def validated(d8a : {{classname}}Data, failFast : Boolean) : scala.util.Try[{{classname}}] = d8a.validated(failFast)

  def fromJson(jason : ujson.Value) : {{classname}}Data = try {
        val data = read[{{classname}}Data](jason)
        {{^isAdditionalPropertiesTrue}}
        data
        {{/isAdditionalPropertiesTrue}}
        {{#isAdditionalPropertiesTrue}}
        val obj = jason.obj
        {{classname}}.Fields.values.foreach(v => obj.value.subtractOne(v.fieldName))
        data.copy(additionalProperties = obj)
        {{/isAdditionalPropertiesTrue}}
    } catch {
      case NonFatal(e) => sys.error(s"Error creating {{classname}}Data from json '$jason': $e")
  }

  def fromJsonString(jason : String) : {{classname}}Data = {
        val parsed = try {
           read[ujson.Value](jason)
        } catch {
          case NonFatal(e) => sys.error(s"Error parsing json '$jason': $e")
        }
        fromJson(parsed)
  }

  def manyFromJsonString(jason : String) : Seq[{{classname}}Data] = try {
        read[List[{{classname}}Data]](jason)
    } catch {
        case NonFatal(e) => sys.error(s"Error parsing json '$jason' as list: $e")
    }

  def manyFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Seq[{{classname}}]] = {
      Try(manyFromJsonString(jason)).flatMap { list =>
        list.zipWithIndex.foldLeft(Try(Vector[{{classname}}]())) {
          case (Success(list), (next, i)) => 
            next.validated(failFast) match {
              case Success(ok) => Success(list :+ ok)
              case Failure(err) => Failure(new Exception(s"Validation error on element $i: ${err.getMessage}", err))
            }
          case (fail, _)  => fail
        }
      }
    }

  def mapFromJsonString(jason : String) : Map[String, {{classname}}Data] = try {
        read[Map[String, {{classname}}Data]](jason)
    } catch {
        case NonFatal(e) => sys.error(s"Error parsing json '$jason' as map: $e")
    }


  def mapFromJsonStringValidated(jason : String, failFast : Boolean = false) : Try[Map[String, {{classname}}]] = {
     Try(mapFromJsonString(jason)).flatMap { map =>
       map.foldLeft(Try(Map[String, {{classname}}]())) {
         case (Success(map), (key, next)) =>
           next.validated(failFast) match {
             case Success(ok) => Success(map.updated(key, ok))
             case Failure(err) => Failure(new Exception(s"Validation error on element $key: ${err.getMessage}", err))
           }
         case (fail, _) => fail
       }
     }
  }
}
